Today, I will introduce contentChild that is the signal counterpart of @ContentChild decorator.
ContentChild
裝飾器傳回一個實例,而 contentChild
函數傳回一個訊號。ContentChild
裝飾器的 static
屬性從 contentChild
函數中刪除。contentChild
有 2 個變體: conentChild.required()
和 contentChild()
。
contentChild.required()
表示該元素在範本中至少出現一次,並且檢索第一個元素。 此函數的傳回類型是 Signal<T>
。contentChild()
表示該元素未出現在範本中且可能是未定義的。函數的傳回類型為 Signal<T|undefined>
。在下面的例子中,我展示了 contentChild
如何透過範本變數 (template variables)、ngTemplates 和 Angular 組件查詢元素。
import { Component, contentChild, effect, ElementRef, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-query-by-variable',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<h3>Query ContentChild by variables</h3>
<label for="color">
<span>Color: </span>
<select id="color" name="color" [(ngModel)]="color">
<option value="">----</option>
<option value="red">red</option>
<option value="yellow">yellow</option>
</select>
</label>
<ng-content select="[header]">Default Header</ng-content>
<ng-content></ng-content>
<ng-content select="[footer]">Default Footer</ng-content>
</div>
`,
})
export class AppQueryByVariableComponent {
color = signal('');
}
AppQueryByVaraibleComponent
元件有三個 <ng-content />
組件,分別用於頁首、頁尾和正文。 它還具有一個下拉式選單來選擇元素的背景顏色。
<app-query-by-variable>
<div #header header class="projection">Custom Header 2</div>
<div #body class="projection">Custom Body</div>
<div #footer footer class="projection">
<ul>
<li>Custom Footer</li>
<li>Custom Footer 2</li>
</ul>
</div>
</app-query-by-variable>
在 App
組件中,div 元素被投影到 <app-query-by-variable>
的 <ng-content>
。第一個 div 元素的範本變數是 #header
,並投影到命名 header
的 ng-content
。第二個 div 元素的範本變數是#body
,並投影到預設的 ng-content
。最後一個 div 元素的範本變數是 #header
,並投影到命名 footer
的 ng-content
。
header = contentChild('header', { read: ElementRef });
footer = contentChild('footer', { read: ElementRef });
body = contentChild('body', { read: ElementRef });
contentChild
函數透過範本變數查詢元素。 第二個參數 { read: ElementRef }
期望函數會擷取 ElementRef
。
constructor() {
effect(() => {
const color = this.color();
const header = this.header()?.nativeElement;
const footer = this.footer()?.nativeElement;
const body = this.body()?.nativeElement;
if (header) {
header.style.backgroundColor = color;
}
if (footer) {
footer.style.backgroundColor = color;
}
if (body) {
body.style.backgroundColor = color;
}
});
}
當 color
signal 更新時,effect
會執行邏輯來變更背景顏色的 CSS 樣式。我使用了 contentChild
而不是 contentChild.required
;因此, signal
值可能是 undefined
的。每個 if-block
在將顏色指派給 backgroundColor
屬性之前都會比較 signal
是否 defined
。
@Directive({
selector: '[someDirective]',
standalone: true,
})
export class AppSomeDirective {
template = contentChild.required(TemplateRef);
}
我們可以寫一個 AppSomeDirective
指令 (directive),使用 contentChild
來查詢投影的 ngTemplate。
@Component({
selector: 'app-query-by-type',
standalone: true,
imports: [AppSomeDirective, NgTemplateOutlet],
template: `
<div class="container">
<h3>Query ContentChild by TemplateRef</h3>
<div someDirective>
<ng-template>
<p>Projected item</p>
<p>Projected item 2</p>
<p>Projected Item 3</p>
</ng-template>
</div>
<ng-content>Default Header</ng-content>
<ng-container *ngTemplateOutlet="template()"></ng-container>
<ng-container *ngTemplateOutlet="directive().template()"></ng-container>
</div>
`,
})
export class AppQueryByDirectiveComponent {}
div 元素有一個 someDirective
屬性;因此,directive 的 contentChild
可以檢索由三個段落元素組成的 ngTemplate。該組件還有一個預設的 <ng-content>
,它可以使用 contentChild
查詢另一個 ngTemplate。
<ng-container *ngTemplateOutlet="template()"></ng-container>
<ng-container *ngTemplateOutlet="directive().template()"></ng-container>
ngTemplates
被指派給 ngTemplateOutlet
指令來呈現動態內容。
export class AppQueryByDirectiveComponent {
directive = viewChild.required(AppSomeDirective);
template = contentChild.required(TemplateRef);
}
AppQueryByDirectiveComponent
元件使用 viewChild
函數來查詢指令 (directive) 並存取其 contentChild
。 contentChild.required
查詢預設 <ng-content>
中存在的 TemplateRef
。
<app-query-by-type>
<ng-template>Custom Header 3</ng-template>
</app-query-by-type>
在 App
元件中,ngTemplate
被投影到 <app-query-by-type>
中,這樣 contentChild.required
函數就不會拋出錯誤。
import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';
const imgURL = 'https://picsum.photos/300/200';
@Component({
selector: 'app-photo',
standalone: true,
template: `
<div class="photo">
<img [src]="img()" alt="Random picture" />
</div>
`,
})
export default class AppPhotoComponent {
#random = signal(Date.now());
img = computed(() => `${imgURL}?random=${this.#random()}`)
loadImage() {
this.#random.set(Date.now());
}
}
AppPhotoComponent
組件具有附加到圖像 URL 的 random seed signal。 當 loadImage
函數更新signal 值時,img
computed signal 會產生一個新的圖片 URL。
import { Component, ChangeDetectionStrategy, contentChild } from '@angular/core';
import AppPhotoComponent from './photo.component';
@Component({
selector: 'app-photo-wrapper',
standalone: true,
template: `
<div class="photo-wrapper">
<ng-content />
<button (click)="changeImage()">Change photo</button>
</div>
`,
})
export default class AppPhotoWrapperComponent {
photo = contentChild.required(AppPhotoComponent);
changeImage() {
this.photo().loadImage();
}
}
AppPhotoWrapperComponent
組件包含一個預設的 <ng-content>
,我們可以投影 AppPhotoComponent
組件。此組件使用 contentChid.required
函數來查詢 AppPhotoComponent
組件。 changeImage
方法呼叫 loadImage
方法來顯示新圖片。
<app-photo-wrapper>
<!-- <app-photo-size size /> -->
<app-photo style="margin-bottom: 0.25rem;" />
</app-photo-wrapper>
在 <app-photo-wrapper>
標籤的內部,有一個 <app-photo>
標籤,並將對應的照片組件投影到<ng-content>
。 contentChild.required
函式成功查詢 AppPhotoWrapperComponent
中的 AppPhotoComponent
。
contentChild
可以查詢元素、ngTemplates、指令 (directive) 和組件。第一個參數是一個 selector,它是範本變數 (template variables) 或類型。read
屬性指定 contentChild
要傳回的元素類型contentChild.required
函數。否則,我們應該使用 contentChild
函數,它可以傳回 signal
或 undefined
。contentChild
函數的 selector 與範本中的多個元素相符時,傳回第一個。content.required
無法查詢某個元素,則函數會拋出錯誤。鐵人賽的第 21 天就這樣結束了。